#pragma once
#include <set>
#include <string>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include "Log.hpp"

class Command
{
public:
    Command(std::string conf_path) : _conf_path(conf_path)
    {
        LoadConf();
    }
    void LoadConf()
    {
        std::ifstream in(_conf_path, std::ios::in); // 读方式打开安全命令的配置文件
        if (!in.is_open())
        {
            std::cout << "LoadConf error\n";
        }

        std::string cmd;
        while (std::getline(in, cmd)) // 读取配置文件
        { 
            _safe_cmd.insert(cmd);
            LOG(DEBUG, "load command [%s] success", cmd.c_str());
        }

        in.close();
    }

    std::string PrefixCmd(const std::string& cmd)
    {
        if (cmd.empty()) return std::string();
        auto pos = cmd.find(' '); // ls -a -l
        if (pos == std::string::npos) return cmd; // ls
        return cmd.substr(0, pos);
    }
    
    bool SafeCheck(const std::string& cmd)
    {
        std::string prefix = PrefixCmd(cmd);
        return _safe_cmd.find(prefix) != _safe_cmd.end();
    }
     
    std::string Excute(const std::string &cmd)
    {
        // 注意：这个函数是线程调用的，线程是进程的一部分，如果执行了exec会让整个进程被替换，因此不推荐用下面的方式直接执行命令
        // fork (pipe  )
        // exec
        // dup

        if (SafeCheck(cmd))
        {
            // FILE* popen(const char* command, const char* type)底层会fork并pipe，将执行结果存放到pipe中
            FILE *pp = popen(cmd.c_str(), "r"); // type指定为r表示需要读取命令执行结果,通过pp指针读取
            if (pp == NULL)
            {
                return "failed\n";
            }
            std::string response; // 存放命令执行结果

            char buffer[1024];

            while (std::fgets(buffer, 1024, pp))
            {
                response += buffer;
            }
            return response;
        }
        else
        {
            return "这是一个不安全的命令\n";
        }
    }

private:
    std::set<std::string> _safe_cmd; // 记录白名单命令
    std::string _conf_path;          // 记录命令白名单路径
};